﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Inovout.Descriptor;

namespace Inovout.Web.Mvc
{
    public class WidgetModelMetadata : CachedDataAnnotationsModelMetadata
    {
        #region 构造器
        public WidgetModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
            : base(provider, containerType, modelType, propertyName, new CachedWidgetMetadataAttributes(attributes))
        {
            PrototypeCache = new CachedWidgetMetadataAttributes(attributes);
        }

        public WidgetModelMetadata(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
            : base(prototype, modelAccessor)
        {
            ModelDeclaringType = prototype.ModelType;
        }
        #endregion

        /// <summary>
        /// Model声明类型
        /// </summary>
        /// <remarks>
        /// Asp.Net Mvc ModelMetadata
        /// if model == null
        ///   ModelType = @model Type
        /// else
        ///   ModelType = model.GetType()
        /// ModelDeclaringType就是为了存储@model Type，不会受model不为null的影响。
        /// </remarks>
        internal Type ModelDeclaringType { get; set; }

        #region Properties
        protected IEnumerable<ModelMetadata> properties;
        public override IEnumerable<ModelMetadata> Properties
        {
            get
            {
                if (properties == null)
                {
                    properties = this.SelectedModelDeclaringTypeProperties ?? this.ModelDeclaringTypeProperties;
                    properties = SortProperties(properties.ToArray());
                }
                return properties;
            }
        }
        private static ModelMetadata[] SortProperties(ModelMetadata[] properties)
        {
            // Performance-senstive
            // Common case is that properties do not need sorting
            int? previousOrder = null;
            bool needSort = false;
            for (int i = 0; i < properties.Length; i++)
            {
                ModelMetadata metadata = properties[i];
                if (previousOrder != null && previousOrder > metadata.Order)
                {
                    needSort = true;
                    break;
                }
                previousOrder = metadata.Order;
            }
            if (!needSort)
            {
                return properties;
            }
            // For compatibility the sort must be stable so use OrderBy rather than Array.Sort
            return properties.OrderBy(m => m.Order).ToArray();
        }

        /// <summary>
        /// 返回ModelType对应的属性ModelMetadata集合。
        /// </summary>
        public IEnumerable<ModelMetadata> ModelTypeProperties { get { return base.Properties; } }

        private IEnumerable<WidgetModelMetadata> modelDclaringTypeProperties;
        public IEnumerable<WidgetModelMetadata> ModelDeclaringTypeProperties
        {
            get
            {
                if (modelDclaringTypeProperties == null)
                {
                    if (ModelType.Equals(this.ModelDeclaringType))
                    {
                        modelDclaringTypeProperties = this.ModelTypeProperties.OfType<WidgetModelMetadata>();
                    }
                    else
                    {
                        var modelDeclaringTypeDescriptor = TypeDescriptor.Get(this.ModelDeclaringType);
                        var modelDclaringTypePropertyNames = modelDeclaringTypeDescriptor.Properties.Select(p => p.Name);
                        modelDclaringTypeProperties = this.ModelTypeProperties.Where(p => modelDclaringTypePropertyNames.Contains(p.PropertyName)).OfType<WidgetModelMetadata>();
                        foreach (var property in modelDclaringTypeProperties)
                        {
                            var propertyDescriptor = modelDeclaringTypeDescriptor.GetProperty(property.PropertyName);

                            //属性的ModelType是属性声明类型，无法合并实例类型。因此，为提高性能，只合并需要的属性，将实例中的Attribute合并
                            property.PrototypeWidgetCache.MergeAttributes(propertyDescriptor.Attributes);
                            property.ModelDeclaringType = propertyDescriptor.DeclaringType;
                        }
                    }
                }
                return modelDclaringTypeProperties;
            }
        }

        public WidgetModelMetadata[] SelectedModelDeclaringTypeProperties { get; private set; }
        internal void SelectModelDeclaringTypeProperties(string[] selectedProperties)
        {

            this.SelectedModelDeclaringTypeProperties = new WidgetModelMetadata[selectedProperties.Length];

            for (int order = 0; order < selectedProperties.Length; order++)
            {
                this.SelectedModelDeclaringTypeProperties[order] = GetProperty(this, selectedProperties[order]);
                this.SelectedModelDeclaringTypeProperties[order].FullPropertyName= selectedProperties[order];
                this.SelectedModelDeclaringTypeProperties[order].PropertyName = selectedProperties[order];
                this.SelectedModelDeclaringTypeProperties[order].Order = order;
            }
            this.properties = null;
        }
        private string fullProeprtyName;
        public string FullPropertyName
        {
            get { return fullProeprtyName??this.PropertyName; }
            private set { fullProeprtyName = value; } }
        internal void SetSelectedModelDeclaringTypeProperties(WidgetModelMetadata[] selectedProperties)
        {
            this.SelectedModelDeclaringTypeProperties = selectedProperties;
            this.properties = null;
        }
        private string propertyName;
        public new string PropertyName
        {
            get { return propertyName ?? base.PropertyName; }
            private set { propertyName = value; }
        }
        public static WidgetModelMetadata GetProperty(WidgetModelMetadata metadata, string proeprtyName)
        {
           int index = proeprtyName.IndexOf(".");
            if (index < 0)
            {
                return metadata.ModelDeclaringTypeProperties.Single(p => p.PropertyName.Equals(proeprtyName));
            }
            return GetProperty(metadata.ModelDeclaringTypeProperties.Single(p => p.PropertyName.Equals(proeprtyName.Substring(0, index))),
                proeprtyName.Substring((index + 1)));
        }
        #endregion


        protected internal CachedWidgetMetadataAttributes PrototypeWidgetCache
        {
            get { return (CachedWidgetMetadataAttributes)base.PrototypeCache; }
        }

        internal void SetModel(object model)
        {
            if (RenderAsFunc == null)
            {
                base.Model = model;
            }
            else
            {
                base.Model = RenderAsFunc(model);
            }
        }
        internal Func<object, object> RenderAsFunc { get; set; }
        public int GridColumnCount { get; internal set; }
        #region DataAnnotations Attribute
        protected string[] ComputeSelectTextValueHtml()
        {
            return PrototypeWidgetCache.Select != null
                      ? PrototypeWidgetCache.Select.TextValueProperties
                      : this.selectTextValueHtml;
        }
        private bool selectTextValueHtmlComputed;
        private string[] selectTextValueHtml;
        public string[] SelectTextValueHtml
        {
            get
            {
                if (!selectTextValueHtmlComputed)
                {
                    selectTextValueHtml = ComputeSelectTextValueHtml();
                    selectTextValueHtmlComputed = true;
                }
                return selectTextValueHtml;
            }
            set
            {
                selectTextValueHtml = value;
                selectTextValueHtmlComputed = true;
            }
        }

        protected string ComputeQueryFormat()
        {
            return PrototypeWidgetCache.QueryFormat != null
                      ? PrototypeWidgetCache.QueryFormat
                      : this.queryFormat;
        }
        private bool queryFormatComputed;
        private string queryFormat;
        public string QueryFormat
        {
            get
            {
                if (!queryFormatComputed)
                {
                    queryFormat = ComputeQueryFormat();
                    queryFormatComputed = true;
                }
                return queryFormat;
            }
            set
            {
                queryFormat = value;
                queryFormatComputed = true;
            }
        }
        #endregion

        internal virtual void Merge(WidgetModelMetadata widgetModelMetadata)
        {
        }

    }

}
